package org.ainlolcat.ainscience.statistics.handlers;

import org.ainlolcat.ainscience.UI.renderer.LineDataRenderer;
import org.ainlolcat.ainscience.UI.renderer.LineRendererSource;
import org.ainlolcat.ainscience.utils.*;
import org.ainlolcat.ainscience.column.Column;
import org.ainlolcat.ainscience.entity.Entity;
import org.ainlolcat.ainscience.statistics.StatisticHandler;
import org.ainlolcat.ainscience.statistics.StatisticStorage;
import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.integration.SimpsonIntegrator;
import org.jdom.Element;

import javax.swing.*;
import java.util.*;
import java.util.List;

/**
 * @author ainlolcat
 * Date: 11/5/11
 */
public class IntegralStatisticHandler implements StatisticHandler , LineRendererSource{
    Hashtable<String, double[][]> data = new Hashtable<String, double[][]>();
    static int BASE_LINE_DEFAULT = 30;
    static int AVERAGE_DOTS_DEFAULT = 0;
    static String DEFAULT_MODE = "manual";

    public JPanel getRenderer() {
        return new LineDataRenderer(this);
    }

    public Hashtable<String,String> getStatistic(Entity entity, Integer columnID) {
        return getStatistic(entity, columnID, null);
    }

    public Hashtable<String, String> getStatistic(Entity entity, Integer columnID, Element params) {
        Hashtable<String,String> stats = new Hashtable<String, String>();
        int baseLineDots = XMLUtils.getSafeInteger4Attribute(params,"baseLineDots", BASE_LINE_DEFAULT );
        int avgIntegralDots = XMLUtils.getSafeInteger4Attribute(params,"avgIntegralDots", AVERAGE_DOTS_DEFAULT );
        String mode = XMLUtils.getSafeString4Attribute(params, "mode", DEFAULT_MODE);
        Column cy = entity.getColumnByOrder(columnID);
        if (cy.isNumeric()){
            Column cx = entity.getXColumn(cy.getAxis());
            if (cx.isNumeric()){
                double[] x = cx.getDoubleRepresentation();
                double[] y = cy.getDoubleRepresentation();
                if (params == null || params.getChildren("range").isEmpty()) {
                    double start = x[0];
                    double end =  x[x.length-1];
                    String name = "Integral " + start + " " + end;
                    Double answer = integrateWholeRange(
                            x,
                            y,
                            baseLineDots,
                            avgIntegralDots,
                            name,
                            mode);
                    StatisticStorage.getInstance().registerHandler(entity,name,this);
                    stats.put(name, answer.toString());
                }
                else {
                    for (Element integral:(List<Element>)params.getChildren("range")){
                        //try retrieve settings from params
                        double start = XMLUtils.getSafeDouble4Attribute(integral, "start", x[0]);
                        double end = XMLUtils.getSafeDouble4Attribute(integral, "end", x[x.length-1]);
                        String name = XMLUtils.getSafeString4Attribute(integral, "name", "Integral " + start + " " + end);

                        Double answer = integrate(
                                x, y,
                                start, end,
                                baseLineDots,
                                avgIntegralDots,
                                name,
                                mode);
                        StatisticStorage.getInstance().registerHandler(entity,name,this);
                        stats.put(name, answer.toString());
                    }
                }
            }
        }
        return stats;
    }

    private double integrateWholeRange(double[] x, double[] y, int baseLineDots, int avgIntegralDots, String name, String mode){
        return integrate(x,y,x[0], x[x.length-1], baseLineDots, avgIntegralDots, name, mode);
    }

    private interface Integrator{
        public double integrate(double[] x, double[] y, double start, double end,  int baseLineDots, int avgIntegralDots , String name);
    }
    private static Map<String, Integrator> INTEGRATOR_MAP = new HashMap<String, Integrator>();
    static {
        INTEGRATOR_MAP.put("common_simpson", new Integrator() {
            public double integrate(final double[] x, double[] y, double start, double end, int baseLineDots, int avgIntegralDots, String name) {
                final int[] subSetInfo = ArraysUtil.getSubsetInfo(x, start, end);
                final double[] strY = ArraysUtil.extractStraightLine(x, y, ArraysUtil.getStraightBaseLine(x, y, subSetInfo, baseLineDots), subSetInfo);
                SimpsonIntegrator simpsonIntegrator = new SimpsonIntegrator();
                simpsonIntegrator.setRelativeAccuracy(0.01);
                try {
                    return simpsonIntegrator.integrate(new UnivariateRealFunction() {
                        public double value(double v) throws FunctionEvaluationException {
                            int idx = 0;
                            double delta = Math.abs(x[0] - v);
                            for (int i=1;i<x.length;i++){
                                double delta2 = Math.abs(x[i] - v);
                                if (delta2 < delta){
                                    delta = delta2;
                                    idx = i;
                                }
                            }
                            int yIdx = idx - subSetInfo[1];
                            if (yIdx < 0 || yIdx > strY.length-1)
                                throw new FunctionEvaluationException(yIdx);
                            return strY[yIdx];
                        }
                    },start, end);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    private double integrate(double[] x, double[] y, double start, double end,  int baseLineDots, int avgIntegralDots , String name, String mode){
        if (DEFAULT_MODE.equals(mode)){
            double[][] data = ArraysUtil.integrateWithOutBaseLine(x, y, start, end, baseLineDots);

            this.data.put(name,data);
            int dataSetSize = data[0].length;

            if (avgIntegralDots > 0){
                if (avgIntegralDots > dataSetSize/3 ) {
                    avgIntegralDots = dataSetSize/3;
                }
                double integral = 0;
                for (int i=0;i<avgIntegralDots;i++){
                    integral += data[2][dataSetSize - 1 - i];
                }
                return integral/avgIntegralDots;
            } else {
                return data[2][dataSetSize-1];
            }
        } else {
            return INTEGRATOR_MAP.get(mode).integrate(x ,y ,start, end, baseLineDots, avgIntegralDots, name);
        }
    }

    public Collection<String> getNames() {
        return data.keySet();
    }

    public double[][] getData(String name) {
        return data.get(name);
    }
}
